import re

from django.conf import settings
from django.utils.module_loading import import_string
from django.urls.resolvers import URLResolver, URLPattern

from oracle.models import OSettingUrl


class RouterHandler(object):
    def get_all_url(self):
        """
        获取项目中所有的URL(必须有name)
        :return:
            字典类型数据，固定包含"_order"键，值为各name扫描的顺序。然后是各name键，值为 {'name': name, 'url': url}
        """
        # 自定义有序字典， _order 保存字典顺序， 简单数据不使用系统OrderDict
        # python36 以后实现的原因，字典默认有序
        url_ordered_dict = {"_order": list()}
        # 导入根目录路由信息
        md = import_string(settings.ROOT_URLCONF)
        # 递归获取路由信息，信息保存在 url_ordered_dict 中
        self.recursion_urls(None, '/', md.urlpatterns, url_ordered_dict)
        return url_ordered_dict

    def recursion_urls(self, pre_namespace, pre_url, urlpatterns, url_ordered_dict):
        """
        递归的获取URL
        :param pre_namespace: namespace前缀，以后用户拼接name
        :param pre_url: url前缀，以后用于拼接url
        :param urlpatterns: 路由关系列表
        :param url_ordered_dict: 保存递归中获取的所有路由
        :return:
        """
        for item in urlpatterns:
            # 非路由分发， 将路由添加到url_ordered_dict
            if isinstance(item, URLPattern):
                # 没有定义名称不处理
                if not item.name:
                    continue
                # 有名称空间拼接名称空间
                if pre_namespace:
                    name = f"{pre_namespace}:{item.name}"
                # 没有名称空间直接确定名称
                else:
                    name = item.name
                # 拼接url字符串
                url = f"{pre_url}{item.pattern.regex.pattern}"
                # 替换url字符串中的^ $ \\/ 这些特殊符号
                url = url.replace("^", "").replace("$", "").replace("\\/", "/")
                # 检查是在自动发现的例外列表中则跳过不再处理
                if self.check_url_exclude(url):
                    continue
                url_ordered_dict["_order"].append(name)
                url_ordered_dict[name] = {'name': name, 'url': url}
            # 路由分发，递归操作
            elif isinstance(item, URLResolver):
                # 格式化名称空间
                if pre_namespace:
                    if item.namespace:
                        namespace = f"{pre_namespace}:{item.namespace}"
                    else:
                        namespace = pre_namespace
                else:
                    if item.namespace:
                        namespace = item.namespace
                    else:
                        namespace = None
                # url合并
                url = f"{pre_url}{item.pattern}"
                # 递归查找url
                self.recursion_urls(namespace, url, item.url_patterns, url_ordered_dict)

    def check_url_exclude(self, url):
        """
        自动发现例外列表
        :param url: url信息
        :return:
            如果在排除列表中返回True
            否则返回None
        """
        # 获取数据库以及配置文件中的 自动检查排除列表
        exclude_url = OSettingUrl.objects.filter(status=0, type=1).values_list('url', flat=True)
        exclude_url = set(settings.AUTO_DISCOVER_EXCLUDE) | set(exclude_url)
        # 检查当前url是否和排除url匹配，如果匹配返回True
        for regex in exclude_url:
            if re.match(regex, url):
                return True
